package org.example.winter;

import org.example.winter.util.StringUtils;
import org.reflections.Reflections;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Bean工厂的默认实现
 *
 * @author YHG
 * @date 2020/8/21 16:51
 */
public class DefaultBeanFactory implements BeanFactory, BeanDefinitionRegistry {

    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    private Map<String, Object> beanMap = new ConcurrentHashMap<>();

    private String rootPath;

    public DefaultBeanFactory(String path) {
        this.rootPath = path;
    }

    @Override
    public void run() {
        Reflections reflections = new Reflections(this.rootPath);
        Set<Class<?>> component = reflections.getTypesAnnotatedWith(Component.class);
        for (Class<?> aClass : component) {
            GeneralBeanDefinition beanDefinition = new GeneralBeanDefinition();
            beanDefinition.setBeanClass(aClass);
            this.registerBeanDefinition(StringUtils.firstToLowerCase(aClass.getSimpleName()), beanDefinition);
        }
    }



    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
        Objects.requireNonNull(beanName, "beanName为空");
        Objects.requireNonNull(beanDefinition, "beanDefinition为空");
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public BeanDefinition getBeanDefinition(String beanName) {
        return this.beanDefinitionMap.get(beanName);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return this.beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public Object getBean(String beanName) {
        return this.doGetBean(beanName, null);
    }

    @Override
    public <T> T getBean(String beanName, Class<T> requiredType) {
        return this.doGetBean(beanName, requiredType);
    }

    /**
     * 获取Bean
     *
     * @param beanName
     * @param requireType
     * @param <T>
     * @return
     */
    protected <T> T doGetBean(String beanName, Class<T> requireType) {
        Objects.requireNonNull(beanName, "beanName为空");
        Object bean = beanMap.get(beanName);
        if (bean != null) {
            return this.transform(bean, requireType);
        }
        BeanDefinition beanDefinition = this.beanDefinitionMap.get(beanName);
        Objects.requireNonNull(beanDefinition, "找不到对应的beanDefinition");
        bean = this.createBeanInstance(beanDefinition);
        this.invokeAwareMethods(beanName, bean);
        populateBean(beanName, beanDefinition, bean);
        if (beanDefinition.isSingleton()) {
            this.beanMap.put(beanName, bean);
        }
        return this.transform(bean, requireType);
    }

    /**
     * 类型转换
     *
     * @param object
     * @param requireType
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    private <T> T transform(Object object, Class<T> requireType) {
        Objects.requireNonNull(object);
        if (requireType != null) {
            return requireType.cast(object);
        }
        return (T) object;
    }

    /**
     * 实例化
     *
     * @param beanDefinition
     * @return
     */
    private Object createBeanInstance(BeanDefinition beanDefinition) {
        Object beanInstance = null;
        try {
            beanInstance = beanDefinition.getBeanClass().newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return beanInstance;
    }

    /**
     * 应用各种Aware接口
     *
     * @param beanName
     * @param bean
     */
    protected void invokeAwareMethods(final String beanName, final Object bean) {
        if (bean instanceof BeanNameAware) {
            ((BeanNameAware) bean).setBeanName(beanName);
        }
    }

    /**
     * 属性注入
     *
     * @param beanName
     * @param beanDefinition
     * @param bean
     */
    protected void populateBean(String beanName, BeanDefinition beanDefinition, Object bean) {
        if (Objects.isNull(bean)) {
            return;
        }
        Class<?> beanClass = beanDefinition.getBeanClass();
        Field[] fields = beanClass.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(AutoInject.class)) {
                try {
                    field.setAccessible(true);
                    field.set(bean, getBean(field.getName()));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
